feat(planner): temporal — the deinterlace engine (epistemic policy + two causal axes)#468
Conversation
…c policy + two causal axes)
`lance-graph-planner::temporal`. Merges the four asynchronous frames (lance
versions / SurrealQL knowable_from / ractor V_ref / cognitive trajectory) into
one causally-coherent SoA — the standing wave a reader deliberates over.
Query-time epistemic policy (NOT storage, NOT ogar-vocab — these classify how a
reader saw a row, not what a class is):
- EpistemicMode {Strict, Aware, Retro} + for_rung + admits
- TemporalStatus {Contemporary, Anachronistic, Spoiler, Unknowable}
- QueryReference {server_id, ref_version, hlc_tick: Option<u64>, mode, rung}
- classify(row_version, knowable_from, v_ref) -> TemporalStatus (TIME-causal)
Both deferred axes are type-visible from day one with trivial single-server
bodies (avoids the emitted_at_millis decision-#4 non-Option trap on both):
- TIME cross-server: QueryReference carries server_id + hlc_tick: Option<u64>;
the cluster-bus policy wakes them with no signature change.
- DATA-causal: the DependsClosure trait (+ NoDeps trivial impl) is the seam the
SPO depends_on/reads_field source plugs into; Rubicon's KausalSpec::Depends
guard implements it (opaque to the producer, like CommitHook to the membrane).
classify_ready + deinterlace are the two-axis entries; deinterlace yields the
dispatchable standing-wave projection (admitted on TIME && ready on DATA),
ordered by the HLC deinterlace key.
knowable_from meet-point: sourced by ogar-adapter-surrealql (DEFINE TABLE),
consumed here by classify — nowhere else.
13 tests green; module is pure (no new deps).
https://claude.ai/code/session_01VysoWJ6vsyg3wEGc5v7T5v
📝 WalkthroughWalkthroughThis PR introduces a new ChangesTemporal Module: Deinterlacing and Epistemic Reasoning
🎯 4 (Complex) | ⏱️ ~60 minutes
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db9249aecb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| #[derive(Debug, Clone, Default)] | ||
| pub struct DepClosure { |
There was a problem hiding this comment.
Make empty dependency closures ready by default
Because DepClosure derives Default, DepClosure::default() produces an empty dependency list with satisfied == false. In any DependsClosure implementation that naturally returns the default for “no dependencies”, deinterlace will drop every otherwise contemporary row via Classification::dispatchable, even though the empty/no-deps case is documented and implemented by NoDeps as ready.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in the latest push: DepClosure no longer derives Default; the manual impl delegates to DepClosure::ready() so the trivial/empty case is satisfied: true (matching NoDeps). Regression: dep_closure_default_is_ready_not_blocking.
| }) | ||
| .cloned() | ||
| .collect(); | ||
| out.sort_by_key(|r| (r.hlc_tick().unwrap_or(0), r.lance_version())); |
There was a problem hiding this comment.
Use lance_version as the missing-HLC sort key
For mixed inputs where some rows have an HLC tick and older/single-server rows do not, unwrap_or(0) forces every missing-HLC row ahead of all HLC rows regardless of its Lance version. That contradicts the documented fallback to lance_version and can return a non-causal projection during partial HLC rollout or when combining legacy rows with new cross-server rows.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in the latest push: sort key is now (r.hlc_tick().unwrap_or_else(|| r.lance_version()), r.lance_version()) — honors the documented fallback. Regression: deinterlace_mixed_hlc_falls_back_to_lance_version asserts a legacy row with lance_version=750 interleaves between HLCs 500 and 900 (it would have clustered at index 0 before).
…llback to lance_version Two real foot-guns codex caught on #468; both fixed + regression-tested (15/15 green). P2 #1 — DepClosure::default() blocked every row. The derived Default produced satisfied: false, so any consumer using ..Default::default() (or default()) would silently make deinterlace drop every contemporary row via Classification::dispatchable. The trivial/empty case IS the ready case (matches DepClosure::ready + NoDeps). Replaced derive with a manual Default that delegates to ready(); regression: dep_closure_default_is_ready_not_blocking. P2 #2 — unwrap_or(0) on hlc_tick forced every missing-HLC row ahead of all HLC rows. The doc says "falls back to lance_version" but the code used 0. During partial cross-server rollout (mixed HLC + legacy) this returned a non-causal projection that clustered all legacy rows at the start regardless of their version. Changed to unwrap_or_else(|| r.lance_version()) so single-server / legacy rows sort by their own version; cross-server rows sort by HLC; mixed inputs interleave on the unified scale. Regression: deinterlace_mixed_hlc_falls_back_to_lance_version. https://claude.ai/code/session_01VysoWJ6vsyg3wEGc5v7T5v
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/lance-graph-planner/src/temporal.rs (1)
140-157: 💤 Low valueConsider refactoring free functions to methods on
QueryReference.Per the coding guideline "Use only method calls on the carrier struct that holds the state, never free functions. Carrier pattern:
trajectory.resolve()instead ofresolve(trajectory, config, awareness)", these free functions could be methods onQueryReference:
classify(row_version, knowable_from, v_ref)→v_ref.classify(row_version, knowable_from)classify_ready(subject, row_version, knowable_from, v_ref, deps)→v_ref.classify_ready(subject, row_version, knowable_from, deps)deinterlace(rows, v_ref, deps)→v_ref.deinterlace(rows, deps)This would align with the repository's carrier pattern convention where the state-holding struct drives method calls.
Also applies to: 254-269, 287-320
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/lance-graph-planner/src/temporal.rs` around lines 140 - 157, The free functions classify, classify_ready, and deinterlace should be moved as methods onto the carrier struct QueryReference to follow the repository's carrier pattern: rename and implement classify(&self, row_version: LanceVersion, knowable_from: LanceVersion), classify_ready(&self, subject: SubjectType, row_version: LanceVersion, knowable_from: LanceVersion, deps: DepsType) and deinterlace(&self, rows: RowsType, deps: DepsType) (use the concrete parameter types from the originals) and migrate the logic from the existing free functions into these methods; update all callers to call v_ref.classify(...), v_ref.classify_ready(...), and v_ref.deinterlace(...) and remove the free-function definitions (also apply the same refactor for the other occurrences referenced in the review, e.g., the functions around the 254–269 and 287–320 ranges) so state is accessed via &self on QueryReference rather than passed in as separate arguments.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/lance-graph-planner/src/temporal.rs`:
- Around line 140-157: The free functions classify, classify_ready, and
deinterlace should be moved as methods onto the carrier struct QueryReference to
follow the repository's carrier pattern: rename and implement classify(&self,
row_version: LanceVersion, knowable_from: LanceVersion), classify_ready(&self,
subject: SubjectType, row_version: LanceVersion, knowable_from: LanceVersion,
deps: DepsType) and deinterlace(&self, rows: RowsType, deps: DepsType) (use the
concrete parameter types from the originals) and migrate the logic from the
existing free functions into these methods; update all callers to call
v_ref.classify(...), v_ref.classify_ready(...), and v_ref.deinterlace(...) and
remove the free-function definitions (also apply the same refactor for the other
occurrences referenced in the review, e.g., the functions around the 254–269 and
287–320 ranges) so state is accessed via &self on QueryReference rather than
passed in as separate arguments.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2d60a5f4-7e8e-4587-b8f5-d881d724a5ee
📒 Files selected for processing (2)
crates/lance-graph-planner/src/lib.rscrates/lance-graph-planner/src/temporal.rs
Tailored cross-repo view onto the substrate-b endgame architecture
from lance-graph's runtime-side perspective. Master doc lives in
AdaWorldAPI/OGAR/docs/SUBSTRATE-ENDGAME.md (five-rooms forward-looking
architecture, 1345 lines). This view focuses on:
- Room 1: lance-graph-contract, lance-graph-ontology, callcenter
(commit_event sibling — PR #467, merged), planner::temporal
(PR #468), supervisor, cognitive-shader-driver
- Room 2: Kanban polyglot work-item trait, HTTP-sidecar bridge,
§14 oracle harness, optional BEAM bridge
- Room 3: callcenter actors per OGAR Class, version_watcher push
stream, RubiconWriter Phase 2 dynamic regeneration for OP's
Workflow table
- Room 4: OpenTelemetry exposition, cognitive-event-row stream
for sexy-tier viz, actor-tree topology API
- Room 5: stable public API + SemVer, runtime getting-started,
reference deployment
Plus cross-references to OGAR's ADR doc (ADR-008 commit_event,
ADR-009 temporal two-axis, ADR-018 Kanban polyglot are lance-graph-
touching decisions) + companion runtime references + open items
lance-graph owns + compact priority map.
Pure docs; navigation aid for runtime sessions picking up the
endgame architecture without re-reading the full OGAR master.
https://claude.ai/code/session_01PBTGaPCSnnt6u3pjXpbLwY
…g, lint CodeRabbit / Codex review addressed: 1. soa_envelope::verify_layout() — P2 correctness fix Previously only checked that column widths *sum* to the declared stride. A column whose end offset exceeds the stride (e.g. two 4-byte columns at offsets 4 and 8 with stride 8) passed the sum check but placed data outside every row. Now each column's end offset is checked against stride directly before the pairwise overlap scan. New test: column_past_stride_caught. Total: 8 tests, all green. 2. soa-three-tier-model.md — Major: target-state vs current-state Sections that asserted "there is no baton/emission" in present tense while MailboxSoA::emit() still exists in source now carry explicit "Target state:" / "Current state:" labels. The removal is scheduled, not yet landed — the doc now says so. 3. CLAUDE.md — Major: patch-warning wording "treat it as a build error to fix" was too absolute; transitive semver mismatch is a legitimate cause. Reworded to "policy alert — verify direct deps and Cargo.lock wiring; track/resolve transitive blockers explicitly." 4. q3-standing-wave-falsification.md — Minor lint Blockquote lines with multiple spaces after `>` normalized (MD027). Unlabelled fenced code block at line 252 given `text` tag (MD040). 5. q4-hhtl-audit.md — Minor lint Blockquote spacing normalized (MD027). 6. New plan + epiphany (board hygiene for this session's findings) .claude/plans/cycle-coherent-soa-snapshot-v1.md — Arc-swap COW at column granularity; 6 deliverables D-SOA-SNAP-1..6; the byte-scale complement to temporal.rs row-scale deinterlace (PR #468). .claude/board/EPIPHANIES.md — E-DEINTERLACE-TWO-SCALES prepended. .claude/board/INTEGRATION_PLANS.md — plan entry prepended. https://claude.ai/code/session_0147hSzjmWZDuy2MSQNrhEK5
lance-graph-planner::temporal— the deinterlace engineThe query-time temporal/epistemic policy layer + the deinterlace engine that the Rubicon standing-wave reads depend on. Pure module, 13 tests green, no new deps.
What it owns
ogar-vocab):EpistemicMode {Strict, Aware, Retro}+for_rung+admits;TemporalStatus {Contemporary, Anachronistic, Spoiler, Unknowable}.QueryReference{server_id, ref_version, hlc_tick: Option<u64>, mode, rung}— HLC-aware in the signature, single-server in the body.classify(row_version, knowable_from, v_ref)— the per-row TIME-causal deinterlace decision.deinterlace(rows, v_ref, deps)— merges interlaced frames into the dispatchable standing-wave projection, ordered by the HLC key.Two causal axes, both type-visible / body-trivial
hlc_tick: Option<u64>carried from day one (no breaking change when the cluster bus lands — avoids theemitted_at_millisdecision-Claude/setup adaworld repos 4k pex #4 trap).DependsClosuretrait (+NoDeps) is the seam the SPOdepends_on/reads_fieldsource plugs into. Rubicon'sKausalSpec::Dependsguard implements it — opaque to the producer, likeCommitHookis to the membrane.Meet-point (durable interface)
knowable_fromis sourced byogar-adapter-surrealql(DEFINE TABLEregistration) and consumed here byclassify— nowhere else. Pinned inCROSS_SESSION_COORDINATION.md.Next
Rubicon repoints to consume this (deleting its local
EpistemicMode/QueryReferenceplaceholder — no dual-source), then moves to its durable home.https://claude.ai/code/session_01VysoWJ6vsyg3wEGc5v7T5v
Summary by CodeRabbit
Release Notes
New Features
Chores